Skip to content

node: Add support for pnpm#511

Merged
bbhtt merged 1 commit intoflatpak:masterfrom
nukeop:master
Mar 12, 2026
Merged

node: Add support for pnpm#511
bbhtt merged 1 commit intoflatpak:masterfrom
nukeop:master

Conversation

@nukeop
Copy link
Contributor

@nukeop nukeop commented Feb 24, 2026

Proposed in #383

This PR adds pnpm as a third package manager option for node. The provider parses pnpm lock file, downloads tarballs as flatpak file sources, and at build time, a bundled Python script extracts tarballs and populates pnpm's content-addressable store.

I did some manual testing on this with a real pnpm project. Should work with monorepos with internal dependencies too.

Limitations:

  • only v10 store, will need to be updated if new store version comes out
  • no patching support
  • git dependnecies are cloned but no lockfile patching to redirect to local paths. pnpm install --offline will probably fail for projects with git deps. Same approach as npm's jq-based patching would be needed
  • requiresBuild hardcoded to false, should be extracted from lockfile metadata. Will cause pnpm to skip native addon rebuilds

@github-actions github-actions bot added the node label Feb 24, 2026
@nukeop nukeop marked this pull request as ready for review February 24, 2026 14:41
@nukeop nukeop requested a review from a team as a code owner February 24, 2026 14:41
if not os.path.exists(cas_path):
os.makedirs(cas_dir, exist_ok=True)
with open(cas_path, 'wb') as out:
out.write(data)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs os.chmod(cas_path, member.mode) or something to preserve executable files.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did this:

if is_exec:
            os.chmod(cas_path, 0o755)

@bbhtt
Copy link
Collaborator

bbhtt commented Feb 25, 2026

Before reviewing the code it would be nice if people can try this out for packaging their own projects. That way we can get a better idea of what works and doesn't, it'd get tested as well and you'd have some feedback.

You can keep seperate commits for now but once it is ready it should be split into logical parts with proper commit messages or one single commit.

@andersk
Copy link

andersk commented Feb 25, 2026

I’ve been trying this on Zulip Desktop. I ran into two issues:

  • os.chmod(cas_path, member.mode) is needed as I described above.
  • I had to upgrade app-builder-lib and @electron/rebuild to remove a Git dependency on https://github.com/electron/node-gyp#06b29aafb7708acef8b3669835c8a7857ebc92d2 which this doesn’t support.

Other than that, this seems to work for me.

@nukeop
Copy link
Contributor Author

nukeop commented Feb 25, 2026

Thank you for testing! I've fixed both issues. I'm glad it works for you!

As for commit naming, we can squash when merging, or I can just rebase and squash then.

'Please upgrade to pnpm 8+ and regenerate your lockfile.'
)

if not lockfile_version.startswith(_SUPPORTED_VERSIONS):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should check the major version startswith will make unrelated versions pass through.
major = lockfile_version.split('.', 1)[0].

same for the one below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, done

raise ValueError(f'{lockfile_path}: missing lockfileVersion field')

lockfile_version = str(raw_version)
if lockfile_version.startswith('5'):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, check the major version

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any reason this is separate form the rest? is it for the different message? i'd merge it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I changed that as you suggested.

Comment on lines +31 to +35
print(
f'ERROR: {tarball_path} not found',
file=sys.stderr,
)
sys.exit(1)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you raise an exception here instead of sys.exit?

pkg_name=info['name'],
pkg_version=info['version'],
integrity_hex=info['integrity_hex'],
requires_build=info.get('requires_build', False),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is requires_build actually used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed it altogether, I was weighing whether to support it during my implementation and decided to keep it out of scope for now, and maybe add in another iteration later.

Comment on lines +36 to +38
# All currently supported lockfile versions (v6/v7/v9) use store v10.
# Needs to be updated when a new pnpm major changes the store layout
_STORE_VERSION = 'v10'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should probably be derived from the lockfile version instead of being hardcoded here. we might need to support multiple store versions instead of the latest one and i'm guessing they will be incompatible.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add tests for git resolution?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

@andersk
Copy link

andersk commented Feb 27, 2026

  • I had to upgrade app-builder-lib and @electron/rebuild to remove a Git dependency on https://github.com/electron/node-gyp#06b29aafb7708acef8b3669835c8a7857ebc92d2 which this doesn’t support.

I was able to hack around this without removing the dependency, as follows.

  • Add to generated-sources.json:

    {
        "type": "file",
        "url": "https://codeload.github.com/electron/node-gyp/tar.gz/06b29aafb7708acef8b3669835c8a7857ebc92d2",
        "sha512": "3178339530c41277490774c16ef779b8540effc81a20da351df7a7f2f79fe6babf54754f781eb7baebffb8ee7ef0616c009feb6aebba5c1efd4ba2b8f9a5dcfb",
        "dest-filename": "@electron__node-gyp-10.2.0-electron.1.tgz",
        "dest": "flatpak-node/pnpm-tarballs"
    },
    
  • Add to pnpm-manifest.json:
    "@electron__node-gyp-10.2.0-electron.1.tgz":{"integrity_hex":"3178339530c41277490774c16ef779b8540effc81a20da351df7a7f2f79fe6babf54754f781eb7baebffb8ee7ef0616c009feb6aebba5c1efd4ba2b8f9a5dcfb","name":"@electron/node-gyp","requires_build":false,"version":"10.2.0-electron.1"},

  • Add to build-commands:
    install -Dm644 ../flatpak-node/pnpm-store/v10/index/31/78339530c41277490774c16ef779b8540effc81a20da351df7a7f2f79fe6ba-@electron+node-gyp@10.2.0-electron.1.json ../flatpak-node/pnpm-store/v10/https+++codeload.github.com+electron+node-gyp+tar.gz+06b29aafb7708acef8b3669835c8a7857ebc92d2/integrity.json

Would this be easy to support automatically?

@bbhtt
Copy link
Collaborator

bbhtt commented Mar 2, 2026

readme should be updated.

import sys
import types
from pathlib import Path
from typing import (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i just did a large refactor of moving to python 3.10+ and migrating to standard type hints so these should be updated.

pytest-datadir = "^1.6.1"
pytest-httpserver = "^1.1.3"
pytest-xdist = "^3.6.1"
types-pyyaml = "^6.0.12.20250915"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
types-pyyaml = "^6.0.12.20250915"
types-pyyaml = ">=6.0.12.2,<7"

@nukeop
Copy link
Contributor Author

nukeop commented Mar 5, 2026

I just rebased onto current master and fixed all the type issues. I also updated the node readme, and squashed all commits as suggested.

@WhyKickAmooCow
Copy link

  • I had to upgrade app-builder-lib and @electron/rebuild to remove a Git dependency on https://github.com/electron/node-gyp#06b29aafb7708acef8b3669835c8a7857ebc92d2 which this doesn’t support.

I was able to hack around this without removing the dependency, as follows.

* Add to `generated-sources.json`:
  ```
  {
      "type": "file",
      "url": "https://codeload.github.com/electron/node-gyp/tar.gz/06b29aafb7708acef8b3669835c8a7857ebc92d2",
      "sha512": "3178339530c41277490774c16ef779b8540effc81a20da351df7a7f2f79fe6babf54754f781eb7baebffb8ee7ef0616c009feb6aebba5c1efd4ba2b8f9a5dcfb",
      "dest-filename": "@electron__node-gyp-10.2.0-electron.1.tgz",
      "dest": "flatpak-node/pnpm-tarballs"
  },
  ```

* Add to `pnpm-manifest.json`:
  `"@electron__node-gyp-10.2.0-electron.1.tgz":{"integrity_hex":"3178339530c41277490774c16ef779b8540effc81a20da351df7a7f2f79fe6babf54754f781eb7baebffb8ee7ef0616c009feb6aebba5c1efd4ba2b8f9a5dcfb","name":"@electron/node-gyp","requires_build":false,"version":"10.2.0-electron.1"},`

* Add to `build-commands`:
  `install -Dm644 ../flatpak-node/pnpm-store/v10/index/31/78339530c41277490774c16ef779b8540effc81a20da351df7a7f2f79fe6ba-@electron+node-gyp@10.2.0-electron.1.json ../flatpak-node/pnpm-store/v10/https+++codeload.github.com+electron+node-gyp+tar.gz+06b29aafb7708acef8b3669835c8a7857ebc92d2/integrity.json`

Would this be easy to support automatically?

This looks like an issue with tarball/git sources in general. I am getting the same issue with the github:jeffvli/Node-MPV#32b4d64395289ad710c41d481d2707a7acfc228f package. Adding an integrity property to the dependency in the pnpm lock file gets me as far as:

Downloading https://codeload.github.com/jeffvli/Node-MPV/tar.gz/32b4d64395289ad710c41d481d2707a7acfc228f
Failed to download sources: module feishin: File name too long

when running a build with the generated sources.

@nukeop
Copy link
Contributor Author

nukeop commented Mar 12, 2026

I'm gonna dedicate some time after this PR to make sure it supports anything pnpm can do. And if it can't, at least make sure there's a good reason for that. But in the interest of keeping scope manageable, I'd rather not add anything else to this one.

@bbhtt
Copy link
Collaborator

bbhtt commented Mar 12, 2026

I can merge this once the lint issues are fixed (CI is failing). Seems it has worked for some projects and the rest can be iterated later.

@nukeop
Copy link
Contributor Author

nukeop commented Mar 12, 2026

Ok, I had an old lock and didn't notice. Should be ok now

@bbhtt bbhtt merged commit 37eb3e5 into flatpak:master Mar 12, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants